﻿using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Drawing;

using ITAP.Core.Calculations;
using System.Windows.Forms;
using ITAP.Core.Modelling;

namespace ITAP.ContourBuilder.Manipulation
{
    public abstract class Command : ICommand
    {
        protected bool disableObtainPeripheryState = false;
        /// <summary>
        /// Если true, то команда перестанет получать информацию об изменении состояния клавиатуры и мыши. Таким образом можно заставить "заморозить" 
        /// выполнение команды, например, для вызова меню или вспомогательной формы, выставив значение в true.
        /// </summary>
        public virtual bool DisableObtainPeripheryState
        {
            get { return disableObtainPeripheryState; }
            set { disableObtainPeripheryState = value; }
        }
        /// <summary>
        /// Контекстное меню, которое заполняется в методе <see cref="CreateContextMenu"/> и показывается в методе <see cref="ShowContextMenu"/>. 
        /// </summary>
        protected ContextMenuStrip contextMenu;
        /// <summary>
        /// Состояние клавиатуры и мыши, которое передаётся в команду при любом его изменении.
        /// </summary>
        protected GlobalPeripheryState peripheryState;
        protected ContourControl control;

        /// <summary>
        /// Показывает контекстное меню, которое перед этим необходимо заполнить, перегрузив метод <see cref="CreateContextMenu"/>. 
        /// </summary>
        public virtual void ShowContextMenu()
        {
            this.CreateContextMenu();

            PointToClientDelegate dlg = new PointToClientDelegate(this.control.PointToScreen);
            Point screenPoint = (Point)this.control.Invoke(dlg, new object[] { this.peripheryState.MouseState.State.Location });


            PointDelegate dlg2 = new PointDelegate(contextMenu.Show);

            control.Invoke(dlg2, new object[] { screenPoint });
            disableObtainPeripheryState = true;
        }

        /// <summary>
        /// Создание элемента контекстного меню.
        /// </summary>
        /// <param name="text">Текст элемента меню.</param>
        /// <param name="dlg">Делегат, который будет вызван при щелчке по элементу меню.</param>
        /// <returns>Элемент контекстного меню, который потом можно добавить в контекстное меню.</returns>
        protected ToolStripMenuItem CreateContextMenuItem(string text, ContextMenuItemDelegate dlg)
        {
            ToolStripMenuItem item = new ToolStripMenuItem(text);
            item.Tag = dlg;
            return item;
        }

        /// <summary>
        /// В этом методе ведётся заполнение элементов контекстного меню (см. <see cref="CommandEnterPolyline"/> для примера).
        /// </summary>
        public virtual void CreateContextMenu()
        {
            contextMenu = new ContextMenuStrip();
            contextMenu.ItemClicked += new ToolStripItemClickedEventHandler(contextMenu_ItemClicked);
            contextMenu.Closing += new ToolStripDropDownClosingEventHandler(contextMenu_Closing);
        }

        protected virtual void contextMenu_Closing(object sender, ToolStripDropDownClosingEventArgs e)
        {
            if (e.CloseReason != ToolStripDropDownCloseReason.ItemClicked)
                disableObtainPeripheryState = false;
        }

        protected virtual void contextMenu_ItemClicked(object sender, ToolStripItemClickedEventArgs e)
        {
            contextMenu.Hide();
            ContextMenuItemDelegate dlg = e.ClickedItem.Tag as ContextMenuItemDelegate;
            dlg.Invoke();
        }

        public Command(ContourControl control)
        {
            this.control = control;
        }

        #region ICommand Members

        protected CommandState state;
        public CommandState State
        {
            get { return state; }
        }
        /// <summary>
        /// Событие, которое вызывается при изменении состояния команды, например, при завершении работы команды.
        /// </summary>
        public event ManipulatorEvent StateChanged;

        public virtual void Execute()
        {
            this.state = CommandState.Execute;
        }

        public virtual void Undo()
        {
        }

        public virtual void Redo()
        {
        }

        protected string name;
        public virtual string Name
        {
            get { return name; }
        }

        protected bool canUndo = false;
        public virtual bool CanUndo
        {
            get { return canUndo; }
        }

        protected bool enabled = true;
        public virtual bool Enabled
        {
            get
            {
                return enabled;
            }
            set
            {
                enabled = value;
            }
        }

        /// <summary>
        /// В этот метод передаётся информация о состоянии клавиатуры и мыши.
        /// </summary>
        /// <param name="state"></param>
        public virtual void ObtainPeripheryState(GlobalPeripheryState state)
        {
            this.peripheryState = state;
        }

        /// <summary>
        /// Метод позволяет рисовать команде на <see cref="Graphics"/>.
        /// </summary>
        /// <param name="g"><see cref="Graphics"/> для рисования.</param>
        /// <param name="screenWidth">Ширина области рисования в пикселях.</param>
        /// <param name="screenHeight">Высота области рисования в пикселях.</param>
        /// <param name="modelRectangle">Вмещающий прямоугольник контейнера моделей.</param>
        /// <param name="cameraCenter">Точка модели, в которую направлен взгляд.</param>
        /// <param name="coordinateConverter">Текущий преобразователь координат, который необходимо использовать для конвертации из координат 
        /// экрана в координаты модели и обратно.</param>
        public virtual void DrawOnGraphics(Graphics g, int screenWidth, int screenHeight, RectangleF modelRectangle, ModelPoint cameraCenter, ModelCoordinateConverter coordinateConverter)
        {
        }

        #endregion
        /// <summary>
        /// Вызов этого метода завершает выполнение команды, оповещая манипулятор, что команда успешно выполнена. 
        /// Если действие команды можно отменить (Command.CanUndo), ссылка на неё будет помещена в историю выполненных команд 
        /// для дальнейшей возможной отмены её действий.
        /// </summary>
        public virtual void Exit()
        {
            this.control.ContourContainer.Update();
            this.state = CommandState.EndExecute;
            if (this.StateChanged != null)
                this.StateChanged.Invoke(this, CommandState.EndExecute);
        }

        /// <summary>
        /// Вызов этого метода завершает выполнение команды, оповещая манипулятор, что команда отменена. 
        /// Ссылка на неё не будет помещена в историю выполненных команд.
        /// </summary>
        public virtual void Cancel()
        {
            this.state = CommandState.Canceled;
            if(this.StateChanged != null)
                this.StateChanged.Invoke(this, CommandState.Canceled);
        }

        /// <summary>
        /// Позволяет установить текущий курсор в любом месте выполнения независимо от текущего потока (будь от поток, в котором происходит рисование или 
        /// поток, в котором выполяется метод <see cref="ObtainPeripheryState"/>.
        /// </summary>
        /// <param name="cursor">Курсор, который необходимо выставить.</param>
        public void SetCursor(Cursor cursor)
        {
            SetCursorDelegate dlg = new SetCursorDelegate(this.control.SetCursor);
            this.control.Invoke(dlg, new object[] { cursor });
        }

        /// <summary>
        /// Получение текущей позиции курсора в экранных координатах относительно области рисования.
        /// Например, при щелчке левее области рисования координата X возвращаемой позиции будет меньше 0.
        /// </summary>
        public Point CursorPosition
        {
            get
            {
                PointToClientDelegate dlg = new PointToClientDelegate(this.control.PointToClient);
                object result = this.control.Invoke(dlg, new object[] { Cursor.Position });
                return (Point)result;
            }
        }

        /// <summary>
        /// Конвертирует координаты точки модели в координаты точки области рисования, используя информацию о размерах модели, размерах области рисования и
        /// текущем преобразователе координат из основного контрола.
        /// </summary>
        /// <param name="modelPoint">Точка модели.</param>
        /// <returns>Точка в области рисования.</returns>
        public PointF ModelPointToScreenPoint(ModelPoint modelPoint)
        {
            return this.control.CoordinateConverter.ModelPointToScreenPoint(modelPoint, control.Width, control.Height, control.FullRectangle, control.CameraCenter);
        }

        /// <summary>
        /// Конвертирует координаты точки из области рисования в координаты точки модели, используя информацию о размерах модели, размерах области рисования и
        /// текущем преобразователе координат из основного контрола.
        /// </summary>
        /// <param name="screenPoint">Точка из области рисования.</param>
        /// <returns>Точка модели.</returns>
        public ModelPoint ScreenPointToModelPoint(PointF screenPoint)
        {
            return this.control.CoordinateConverter.ScreenPointToModelPoint(screenPoint, control.Width, control.Height, control.FullRectangle, control.CameraCenter);
        }

        #region ICloneable Members

        public virtual object Clone()
        {
            Command clone = (Command)Activator.CreateInstance(this.GetType(), new object[] { this.control });
            return clone;
        }

        #endregion

        #region ICommand Members

        /// <summary>
        /// Вызывается манипулятором асинхронно для завершения работы команды извне.
        /// </summary>
        public virtual void Terminate()
        {
            this.Exit();
        }

        #endregion

        #region ICommand Members

        /// <summary>
        /// Если true, то манипулятор разрешит запуск нескольких разных копий команды одновременно, иначе будет запущена только одна команда.
        /// </summary>
        public virtual bool AllowMultipleInstances
        {
            get { return false; }
        }

        #endregion

        #region ICommand Members

        /// <summary>
        /// Сохраняет в текущую историю команд копию команды. Это необходимо в случае, когда команда продолжает 
        /// работать, но нужно добавить в список истории команд результат работы этой команды.
        /// </summary>
        public virtual void SaveToHistory()
        {            
            
        }

        #endregion

        #region IExecutable Members

        public ContourControl Control
        {
            get
            {
                throw new NotImplementedException();
            }
            set
            {
                throw new NotImplementedException();
            }
        }

        public virtual Bitmap Icon
        {
            get { return null; }
        }

        #endregion
    }
}
